/**
 *  created by lllwx
 *  Date: 2021/6/2
 *  Time: 1:51 下午
 *  Version: 1.0
 *  For:
 */
import axios, {
  AxiosError, AxiosInstance, AxiosRequestConfig, AxiosResponse, Canceler,
} from 'axios';
import { message } from 'ant-design-vue';
// @ts-ignore
import qs from 'qs';
import { HTTP_STATUS } from '@/common';
import router from '../router/index';

const successCode = [HTTP_STATUS.ACCEPTED, HTTP_STATUS.CREATED, HTTP_STATUS.SUCCESS];

const serverErrorCode = [HTTP_STATUS.SERVER_ERROR, HTTP_STATUS.GATEWAY_TIMEOUT, HTTP_STATUS.BAD_GATEWAY, HTTP_STATUS.SERVICE_UNAVAILABLE];

type CancelRequest = {
  mark: string,
  cancel: Canceler
  routeChangeCancel: boolean
}

// 当前缓存的pending状态的请求
export const pendingRequest: CancelRequest[] = [];

// 不取消的请求
export const notRouteChangeCancel: string[] = [];

export class Request {
  // 唯一实例
  private static instance: undefined | Request;

  // 请求对象
  private service: AxiosInstance;

  // 单例获取
  public static getInstance(): Request {
    this.instance || (this.instance = new Request());

    return this.instance;
  }

  constructor() {
    this.service = axios.create({
      baseURL: import.meta.env.VITE_BASE_URL,
      timeout: Number.parseInt(import.meta.env.VITE_TIME_OUT, 10),
      routeChangeCancel: true,
    });
    this.requestInterceptors();
    this.responseInterceptors();
  }

  // 请求拦截
  protected requestInterceptors(): void {
    this.service.interceptors.request.use(
      async (config: AxiosRequestConfig) => {
        // 请求标志
        const requestMark = `${config.url}/${config.method}`;
        const source = axios.CancelToken.source();
        config.cancelToken = source.token;
        config.requestMark = requestMark;
        // 加入到pending请求队列中
        pendingRequest.push({
          mark: requestMark,
          cancel: source.cancel,
          // 是否在切换路由取消请求 默认true
          routeChangeCancel: !!config.routeChangeCancel,
        });
        return config;
      },
      (error: AxiosError) => Promise.reject(error),
    );
  }

  // 响应拦截
  protected responseInterceptors(): void {
    this.service.interceptors.response.use(
      (response: AxiosResponse) => {
        // 从pending请求队列删除
        const markIndex = pendingRequest.findIndex((item) => item.mark === response.config.requestMark);
        markIndex !== -1 && pendingRequest.splice(markIndex, 1);
        // 200
        if (successCode.indexOf(response.status) !== -1) {
          if (response.headers.contentType === 'blob') {
            return '文件下载';
          }
          // 接口返回200
          if (successCode.indexOf(response.data.code) !== -1) {
            return response.data.data;
          }
          message.error(response.data.message, 2);

          return Promise.reject(response.data);

        }
        if (serverErrorCode.indexOf(response.status) !== -1) {
          // 服务器错误
          message.error('网络繁忙');
        } else {
          // TODO 请求错误
        }
      },
      (error: AxiosError) => {
        if (error.response?.status === HTTP_STATUS.UNAUTHORIZED || error.response?.data.code === HTTP_STATUS.UNAUTHORIZED) {
          router.replace({
            name: 'Login',
          });
          message.error(error.response?.data.message, 2);
        }
        return Promise.reject(error);
      },
    );
  }

  /**
   * @description get请求
   * @param {string} url
   * @param params
   * @param {object} config
   * @returns {Promise<AxiosResponse<any>>}
   */
  public async get<T = any>(url: string, params: any = {}, config: Record<string, string> = {}): Promise<T> {
    return await this.service.get(url, {
      ...config,
      params,
    });
  }

  /**
   * @description delete 请求
   * @param {string} url
   * @param params
   * @returns {Promise<any>}
   */
  public async delete<T = any>(url: string, params: any = {}): Promise<T> {
    return await this.service.delete(url, {
      params,
    });
  }

  /**
   * @description post 请求
   * @param {string} url
   * @param data
   * @param {object} headers
   * @returns {Promise<AxiosResponse<any>>}
   */
  public async post<T = any>(url: string, data: any = {}, headers: Record<string, string> = {}): Promise<T> {
    if (headers['content-type'] === 'application/x-www-form-urlencoded') {
      data = qs.stringify(data);
    }
    if (headers['content-type'] === 'multipart/form-data') {
      data = data.file;
    }
    return await this.service.post(url, data, {
      headers,
    });
  }
}
